from django.contrib.auth.models import UserManager
from django.db import models
from django.contrib.auth.models import User
from django import forms
import re
from django.core import validators
from django.core.exceptions import ValidationError
from operator import mul
import sys

class user_status(models.Model):
    """ Model representing user status """
    name = models.CharField(max_length=255)
    description = models.CharField(max_length=255)
    class Meta:
        verbose_name  ="Status uzytkownika"
        verbose_name_plural = "Statusy uzytkownikow"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class user(User):
    """ Model representing a user """
    status_id = models.ForeignKey(user_status,null=True,blank = True)
    created_at = models.DateField(auto_now_add=True)
    objects = UserManager()
    class Meta:
        verbose_name  ="Uzytkownik"
        verbose_name_plural = "Uzytkownicy"


def wordCharsValidator(word):
    error_message = '{0} is not a valid!'.format(word)
    regex = re.compile(r'\w+$', re.UNICODE)
    if not regex.search(word):
        raise ValidationError(error_message)
def rabatValidator(rabat):
    error_message = " should be in range [0;1)".format(rabat)
    if rabat>=0 and rabat<1:
        return
    else:
        raise ValidationError(error_message)
def PositiveFloatValidator(number):
    error_message = "rabat should be in range [0;inf)".format(number)
    if number>=0 :
        return
    else:
        raise ValidationError(error_message)
    

def pesel_validator(pesel):
    """ PESEL number validator
    Args:
        pesel as string
    Returns:
        Valid pesel
    Throws:
        ValidationError
    """
    error_message = '{0} is not a valid PESEL identifier!'.format(pesel)
    if not re.match('^[0-9]{11}$', pesel):
        raise ValidationError(error_message)
    weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1]
    psum =0
    for i in range(11):
        psum += (int(pesel[i]) * weights[i])
    if (psum % 10) != 0:
        raise forms.ValidationError(error_message)
    return

def nipValidator(nip):
    """
    nipValidator(nip)
    Args:
        nip - number as string
    Returns:
        Valid nip
    Throws:
        ValidationError

    """
    invalid_nip = '{0} is not a valid NIP identifier!'.format(nip)
    if not re.match('^[0-9]{10}$', nip):
        raise ValidationError(invalid_nip)
    weights = [6,5,7,2,3,4,5,6,7]
    psum =0
    for i in range(9):
        psum += (int(nip[i]) * weights[i])
    if (psum % 11) !=  int(nip[9]):
        raise forms.ValidationError(invalid_nip)
    return

def regonValidator(regon):
    """
    regonValidator(regon)
    Args:
        REGON - number as string
    Returns:
        Valid REGON
    Throws:
        ValidationError

    """
    invalid_regon = '{0} is not a valid NIP identifier!'.format(regon)
    weights9    =[8,9,2,3,4,5,6,7]
    weights14   = [2,4,8,5,0,9,7,3,6,1,2,4,8]
    if  re.match('^[0-9]{9}$', regon):
        digits = map(int,regon)
        reminder = (sum(map(mul,digits[0:7],weights9)) % 11) %10
        if reminder != digits[8]:
            raise forms.ValidationError(invalid_regon)
    elif re.match('^[0-9]{14}$', regon):
        digits = map(int,regon)
        reminder = (sum(map(mul,digits[0:12],weights14)) % 11) %10
        if reminder != digits[13]:
            raise forms.ValidationError(invalid_regon)
    else:
        raise forms.ValidationError(invalid_regon)

    return regon



def phone_validator(number):
    """
    phone_validator(number)
    Args:
        number as string
    Returns:
        None
    Throws:
        ValidationError
    """
    error_message = '{0} is not a valid phone number!'.format(number)
    if not re.match('^[0-9+-]{1,13}$', number):
        raise ValidationError(error_message)



class currency(models.Model):
    """ Model representing a currency """
    name = models.CharField(max_length=255)
    symbol_path = models.CharField(max_length=255)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Waluta"
        verbose_name_plural = "Waluty"
    def __unicode__(self):
        return u"%s" % unicode(self.name)


class country(models.Model):
    """ Model representing concrete country """
    name = models.CharField(max_length=255)
    currencies = models.ManyToManyField(currency,through = 'country_currency')

    class Meta:
        verbose_name  ="Kraj"
        verbose_name_plural = "Kraje"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class country_currency(models.Model):
    country = models.ForeignKey(country)
    currency = models.ForeignKey(currency)

class address(models.Model):
    """ Model representing complete address """
    country = models.ForeignKey(country,on_delete = models.PROTECT)
    street = models.CharField(max_length=255)
    street_no = models.PositiveIntegerField()
    house_no = models.PositiveIntegerField()
    city = models.CharField(max_length=255)
    post_code = models.CharField(max_length=255) # XXX validator needed?
    province = models.CharField(max_length=255)
    phone_primary = models.CharField(max_length=255, validators=[phone_validator,])
    phone_secondary = models.CharField(max_length=255, validators=[phone_validator,])
    additional_data = models.TextField(null=True,blank = True)
    
    
    permissions = (('addres_read','addres_read'),)
    class Meta:
        verbose_name  ="Adres"
        verbose_name_plural = "Adresy"
    def __unicode__(self):
        return u"%s | %s | %s" % (unicode(self.country.name),unicode(self.city),unicode(self.street))

class partner_status(models.Model):
    """ Model representing status of the partner """
    name = models.CharField(max_length=255)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Status wspolpracownika"
        verbose_name_plural = "Statusy wspolpracownikow"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class cooperation_group(models.Model):
    """ Model representing cooperation group """
    name = models.CharField(max_length=255)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Grupa wspolpracownikow"
        verbose_name_plural = "Grupy wspolpracownikow"
    def __unicode__(self):
        return u"%s" % unicode(self.name)
    
class partner(models.Model):
    """ Model representing a partner """
    status = models.ForeignKey(partner_status,on_delete = models.PROTECT)
    address = models.ForeignKey(address,on_delete = models.PROTECT)
    name = models.CharField(max_length=255)
    email = models.EmailField()
    phone = models.CharField(max_length=13, validators=[phone_validator,])
    phone2 = models.CharField(max_length=13, validators=[phone_validator,],null=True,blank = True)
    fax = models.CharField(max_length=13, validators=[phone_validator,],null=True,blank = True)
    www = models.CharField(max_length=255,null=True,blank = True)
    nip = models.CharField(max_length=10,validators=[nipValidator,]) # xxx validator needed?
    regon = models.IntegerField(max_length=14,validators=[regonValidator,])
    krs = models.IntegerField(null=True,blank=True)
    rabat = models.BooleanField()
    rabat_value = models.FloatField(validators = [rabatValidator])
    additional_data = models.TextField(null=True,blank = True)
    cooperation_groups = models.ManyToManyField(cooperation_group)
    class Meta:
        verbose_name  ="Wspolpracownik"
        verbose_name_plural = "Wspolpracownicy"
    def __unicode__(self):
        return u"%s" % unicode(self.name)
    
class partner_cooperation_group(models.Model):
    partner = models.ForeignKey(partner)
    cooperation_group = models.ForeignKey(cooperation_group)

class person(models.Model):
    """ Model representing a partner """
    user = models.OneToOneField(user,null=True,blank=True)
    partner = models.ForeignKey(partner,on_delete = models.PROTECT)
    address = models.ForeignKey(address,on_delete = models.PROTECT)
    first_name = models.CharField(max_length=25)
    last_name = models.CharField(max_length=25)
    pesel = models.CharField(max_length=11,validators=[pesel_validator])#error when validating correct pesel
    birth_data = models.DateField()
    email = models.EmailField(null=True,blank = True)
    work_mobile = models.CharField(max_length=13, validators=[phone_validator],null=True,blank = True)
    work_phone = models.CharField(max_length=13, validators=[phone_validator])
    work_email = models.EmailField()
    class Meta:
        verbose_name  ="Osoba"
        verbose_name_plural = "Osoby"
    def __unicode__(self):
        return u"%s | %s | %s| %s | %s" % (unicode(self.first_name),unicode(self.last_name),unicode(self.pesel),
                                        unicode(self.user.user_ptr.username),unicode(self.partner))



class order_state(models.Model):
    """ Model representing state of the model """
    name = models.CharField(max_length=255)
    document_types = models.ManyToManyField('document_type')
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Status zlecenia"
        verbose_name_plural = "Statusy zlecen"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class order_state_document_type(models.Model):
    order_state=  models.ForeignKey(order_state)
    document_type = models.ForeignKey('document_type')

class order_type(models.Model):
    """ Model representing type of an order """
    name = models.CharField(max_length=255)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Typ zlecenia"
        verbose_name_plural = "Typ zlecenia"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class barcode(models.Model):
    """ Model representing a bar code """
    barcode_no = models.CharField(max_length=255)
    path = models.CharField(max_length=255)
    class Meta:
        verbose_name  ="Kod kreskowy"
        verbose_name_plural = "Kody kreskowe"
    def __unicode__(self):
        return u"%s|%s" % (unicode(self.barcode_no),unicode(self.path))

class product_category(models.Model):
    """ Model representing product category """
    name = models.CharField(max_length=255)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Kategoria produktu"
        verbose_name_plural = "Kategorie produktow"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class unit(models.Model):
    """ Model representing a unit """
    name = models.CharField(max_length=255)
    symbol = models.CharField(max_length=10)
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Jednostka"
        verbose_name_plural = "Jednostki"
    def __unicode__(self):
        return u"%s" % unicode(self.name)

class tax_rate(models.Model):
    """ Model representing a tax rate """
    country = models.ForeignKey(country,on_delete = models.PROTECT)
    symbol = models.CharField(max_length=3)
    value = models.FloatField(validators = [rabatValidator])
    description = models.TextField(null=True,blank = True)
    class Meta:
        verbose_name  ="Wartosc podateku"
        verbose_name_plural = "Wartosci Podatkow"
    def __unicode__(self):
        return u"%s | %s" % (unicode(self.symbol),unicode(self.country))
class product(models.Model):
    """ Model representing specific product """
    category = models.ForeignKey(product_category,on_delete = models.PROTECT)
    unit = models.ForeignKey(unit,on_delete = models.PROTECT)
    tax_rate = models.ForeignKey(tax_rate,on_delete = models.PROTECT)
    name = models.CharField(max_length=255)
    symbol = models.CharField(max_length=255)
    is_carefully = models.BooleanField()
    buy_price = models.FloatField(validators = [PositiveFloatValidator])
    sell_price = models.FloatField(validators = [PositiveFloatValidator])
    class Meta:
        verbose_name  ="Produkt"
        verbose_name_plural = "Produkty"
    def __unicode__(self):
        return u"%s" % unicode(self.name)




class order(models.Model):
    """ Model representing an order """
    partner_from        = models.ForeignKey(person,related_name='transaction_partner_from')
    partner_to          = models.ForeignKey(person,related_name='transaction_partner_to')
    storehouse_from     = models.ForeignKey('storehouse',related_name='storehouse_form',null=True,blank=True)
    storehouse_to       = models.ForeignKey('storehouse',related_name='storehouse_to',null=True,blank=True)
    transport           = models.ForeignKey('partner',related_name='transport_partner')
    order_type          = models.ForeignKey(order_type)
    system_user         = models.ForeignKey(user)
    state               = models.ForeignKey(order_state)
    currency            = models.ForeignKey(currency)
    receive_date        = models.DateField()
    release_date        = models.DateField()
    price               = models.FloatField(validators = [PositiveFloatValidator]) # XXX ?
    is_archived         = models.BooleanField()
    is_achievable       = models.BooleanField()
    description         = models.TextField(null=True,blank = True)
    creation_date       = models.DateField(auto_now_add = True)
    """
    Additional permissions conected to model
    """
    permissions = (('LogSup','Nadzor Logistyki'),('OrderCanc','Can Cancel Order'),('order change_r','order change_r'),('supervising order','supervising order'),\
                   ('view own orders','view own orders'),('read only','read only'),('order storehouse man','order storehouse man')
                   )
    class Meta:
        verbose_name  ="Zamowienie"
        verbose_name_plural = "Zamowienia"
    def __unicode__(self):
        return u"%s| %s|%s|%s" % (unicode(self.creation_date),unicode(self.id),unicode(self.system_user),unicode(self.state))
    def clean(self):
        print "order_clean"
        models.Model.clean(self)
    def save(self, force_insert=False, force_update=False, using=None):
        print "Order_save"
        models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using)

class palette_type(models.Model):
    """Model representing a palete_type"""
    name = models.CharField(max_length=255)
    description = models.TextField(null = True,blank = True)
    class Meta:
        verbose_name  ="Typ palety"
        verbose_name_plural = "Typy palet"
    def __unicode__(self):
        return u"%s" % unicode(self.name)
    
class order_product(models.Model):
    """ Model representing a product order """
    product = models.ForeignKey(product,on_delete = models.PROTECT)
    order = models.ForeignKey(order)
    barcode = models.ForeignKey(barcode,null = True,blank = True,on_delete = models.SET_NULL)
    palette_type = models.ForeignKey(palette_type,on_delete = models.PROTECT)
    quantity = models.PositiveIntegerField()
    expiration_date = models.DateField(null =True,blank = True)
    description = models.TextField(null = True,blank = True)
    class Meta:
        verbose_name  ="Produkt w zleceniu"
        verbose_name_plural = "Produkty w zleceniach"
    
    def save(self, force_insert=False, force_update=False, using=None):
        print "order_product_save"
        models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using)
    def clean(self):
        """
        Checks if products can be taken from storehose and if storehouse accept
        given product category
        """
        print "order_product_Clean"
        models.Model.clean(self)
        #print "SFROM:::",self.order.storehouse_from
        #print "ORDER::",self.order
        if self.order.storehouse_from:
            
            storehouse_product_categories = self.order.storehouse_from.product_categories.all()
            get_products = storehouse_product.objects.filter(storehouse = self.order.storehouse_from,palete_type =self.palette_type,product = self.product)
            booked_out_sum =sum( [ booked_out.quantity for booked_out in storehouse_product_book_out.objects.filter(storehouse = self.order.storehouse_from,palete_type =self.palette_type,product = self.product)])
            print "before categories"
            if  not (self.product.category in storehouse_product_categories):
                print "order_product:clean(category)",sys.exc_info()
                raise ValidationError("Magazyn Zrodlowy nie zawiera produktow tej kategorii")
            print "before ilosc"
            if len(get_products)< self.quantity:
                "order_product:clean(quan)",sys.exc_info()
                raise ValidationError("Magazyn Zrodlowy nie zawiera tego produktu w wystarczajacej ilosci")
            print "before book"
            if len(get_products) - booked_out_sum < self.quantity:
                print "order_product:clean(booked)",sys.exc_info()
                raise ValidationError("Magazyn Zrodlowy nie zawiera tego produktu w wystarczajacej ilosci(zostal ju zamowiiony)")            
            
        if self.order.storehouse_to:
            storehouse_product_categories = self.order.storehouse_to.product_categories.all()
            if  not (self.product.category in storehouse_product_categories):
                raise ValidationError("Magazyn docelowy nie przyjmuje produktow tej kategorii")
    def __unicode__(self):
        return u"%s|%s|%s|%s" % (unicode(self.barcode),unicode(self.product),unicode(self.palette_type),unicode(self.quantity))
                           
class storehouse(models.Model):
    """ Model represening a storehouse """
    address = models.ForeignKey(address,on_delete = models.PROTECT)
    symbol = models.CharField(max_length=255)
    name = models.CharField(max_length=255)
    manager = models.ForeignKey(person,unique = True,related_name='storehouse manager',on_delete = models.PROTECT)
    person  = models.ManyToManyField(person,related_name='sotrehouse staff')
    product_categories = models.ManyToManyField(product_category)
    
    class Meta:
        verbose_name  ="Magazyn"
        verbose_name_plural = "Magazyny"
    def __unicode__(self):
        return u"%s" % unicode(self.name)
    
class storehouse_person(models.Model):
    storehouse = models.ForeignKey(storehouse)
    person = models.ForeignKey(person)


class storehouse_volume(models.Model):
    """
    Model representing volume of storehouse
    Volume - how many palettes of given type storehouse can store
    """
    storehouse =  models.ForeignKey(storehouse)
    palete_type = models.ForeignKey(palette_type)
    quantity = models.PositiveIntegerField()
    class Meta:
        verbose_name  ="Oblozenie magazynu"
        verbose_name_plural = "Oblozenie magazynu"
    def __unicode__(self):
        return u"%s %s %s" % (unicode(self.storehouse),unicode(self.palete_type),unicode(self.quantity))



class document_type(models.Model):
    """ Model represening a document """
    name = models.CharField(max_length=255)
    
    symbol = models.CharField(max_length=10)
    description = models.TextField(null = True,blank = True)

    
    class Meta:
        verbose_name  ="Typ dokumentu"
        verbose_name_plural = "Typy dokumentow"
    def __unicode__(self):
        return u"%s" % unicode(self.name)
    
class liquidation(models.Model):
    """
    Model representing liquidation

    """
    description = models.TextField(null = True,blank = True)
    product = models.ForeignKey(product)
    palette_type = models.ForeignKey(palette_type,on_delete = models.PROTECT)
    barcode  = models.ForeignKey(barcode,on_delete = models.PROTECT)
    
    permissions = (('liquidation change_r','liquidation change_r'),('liquidation can view','liquidation can view'))
         
class inventory(models.Model):
    #TODO inventory takes name from  date and storehouse name
    name = models.CharField(max_length=255,null = True)
    description = models.TextField(null = True,blank = True)
    is_applied =  models.BooleanField()
    is_canceled =  models.BooleanField()

    """
    Additional permissions
    """
    permissions = (('can cancel inventory','can cancel inventory'),('can apply inventory','can apply inventory'))
    
    class Meta:
        verbose_name  ="Inwentaryzacja"
        verbose_name_plural = "Inwentaryzacje"
    def __unicode__(self):
        return u"Inventory %s" %(unicode(self.name))
    
class inventory_product(models.Model):
    """
    Model representing inventory product
    """
    inventory = models.ForeignKey(inventory)
    product = models.ForeignKey(product)
    palete_type = models.ForeignKey(palette_type)
    barcode  = models.ForeignKey(barcode)
    
    class Meta:
        verbose_name  ="Inwentaryzowany produkt"
        verbose_name_plural = "Inwentaryzacje produktow"

    
class document(models.Model):
    """
    Model representing document
    """
    document_type = models.ForeignKey(document_type)
    creator = models.ForeignKey(user)
    storehouse  = models.ForeignKey(storehouse)
    order = models.ForeignKey(order,null = True)
    liquidation = models.ForeignKey(liquidation,null = True,blank = True)
    inventory = models.ForeignKey(inventory,null = True,blank = True)
    creationDate = models.DateField(auto_now_add=True)


    ordering = ['-creationDate']
    permissions = (('document can view','document can view'),('document change_r','document change_r'),('document storehouse manager','document storehouse manager'),\
                   ('StoMan','Nadzor Magazynu'),)
    
    class Meta:
        verbose_name  ="Dokument"
        verbose_name_plural = "Dokumenty"

    def clean(self):
        """
        Checki if only one of fields order liquidation inventory is not Null
        """
        error_message = "tylko jedno z pol order liquidation inventory moze byc wybrane"
        if not( bool(self.order is not None)^bool(self.inventory is not None)^bool(self.liquidation is not None)):
            raise ValidationError(error_message)
    def __unicode__(self):
        try:
            if self != None:
                return u"%s/%s/%s" % (unicode(self.document_type),unicode(self.creationDate),unicode(self.id))
            else:
                return u"NONE"
        except:
            pass

class document_modification(models.Model):
    """
    Model representing document modification
    History of chenges
    """
    document = models.ForeignKey(document)
    user = models.ForeignKey(user)
    modificationDate = models.DateField(auto_now_add=True)
    class Meta:
        verbose_name  ="Modyfikacje dokumentow"
        verbose_name_plural = "Modyfikacje dokumentow"

class order_modification(models.Model):
    """
    Model representing order modification
    History of changes
    """
    order = models.ForeignKey(order)
    user = models.ForeignKey(user)
    modificationDate = models.DateField(auto_now_add=True)
    class Meta:
        verbose_name  ="Modyfikacje zlecen"
        verbose_name_plural = "Modyfikacje zlecen"


class storehouse_product(models.Model):
    """
    Model representing products in storehouse
    """
    storehouse = models.ForeignKey(storehouse,on_delete = models.PROTECT)
    product = models.ForeignKey(product)
    palete_type = models.ForeignKey(palette_type)
    barcode  = models.ForeignKey(barcode,unique = True,on_delete = models.PROTECT)
    class Meta:
        verbose_name  ="Produkty w magazynach"
        verbose_name_plural = "Produkty w magazynach"
    def __unicode__(self):
        return u"%s|%s|%s" % (unicode(self.product.name),unicode(self.palete_type),unicode(self.barcode))
    
class inventory_product_diff(models.Model):
    """
    Model representing differences discovered during inventory
    """
    inventory = models.ForeignKey(inventory)
    deficit = models.BooleanField()# true if product should be in storehouse but it isn't
    product = models.ForeignKey(product)
    palete_type = models.ForeignKey(palette_type)
    barcode  = models.ForeignKey(barcode)
    class Meta:
        verbose_name  ="Roznica w inwentaryzacji"
        verbose_name_plural = "Roznice w inwentaryzacjach"
    def __unicode__(self):
        return u"%s|%s|%s" % (unicode(self.product.name),unicode(self.palete_type),unicode(self.barcode))
    
class storehouse_product_book_in(models.Model):
    """
    Model representing products that will be stored in storehouse
    """
    storehouse = models.ForeignKey(storehouse,on_delete = models.PROTECT)
    product = models.ForeignKey(product)
    quantity = models.PositiveIntegerField()
    palete_type = models.ForeignKey(palette_type)
    order = models.ForeignKey(order)
    
    def __unicode__(self):
        return u"%s|%s|%s|%s" % (unicode(self.product.name),unicode(self.palete_type),unicode(self.quantity),unicode(self.order))
    
    
class storehouse_product_book_out(models.Model):
    """
    Model representing products that will be released from storehouse
    """
    storehouse = models.ForeignKey(storehouse,on_delete = models.PROTECT)
    product = models.ForeignKey(product)
    quantity = models.PositiveIntegerField()
    palete_type = models.ForeignKey(palette_type)
    order = models.ForeignKey(order)
    
    def __unicode__(self):
        return u"%s|%s|%s|%s" % (unicode(self.product.name),unicode(self.palete_type),unicode(self.quantity),unicode(self.order))
    
